# -*- coding: utf-8 -*-
"""
@Time    : 2022/8/31 21:29
@Author  : huanghuang
"""
import asyncio
import copy
from io import BytesIO
from typing import List, Any, Dict, Union

from aiohttp import ClientSession, ServerTimeoutError, ClientResponseError, FormData, ClientResponse
from starlette import status


__all__ = ["Client", "init_client"]

_DEFAULT_USER_AGENT = 'Mozilla/5.0 Gecko/20100101 Firefox/53.0 (PYMTK-HCP-2.1)'


_DEFAULT_GET_HEADER = {
    'User-Agent': _DEFAULT_USER_AGENT,
}

_DEFAULT_POST_HEADER = {
     "Content-Type": "application/json; charset=utf-8",
}


class Client:

    def __init__(self, loop, *, timeout: int = 5):
        self.__client = ClientSession(loop=loop)
        self.__timeout = timeout

    @property
    def client(self):
        return self.__client

    @staticmethod
    def _parse_with_json(response, parse_with_json):
        return "json" in response.headers.get("Content-Type") if parse_with_json is None else parse_with_json

    async def close(self):
        await self.__client.close()
        self.__client = None

    async def _request(self, method: str, url: str, **kwargs):
        """

        :param method:
        :param url:
        :param kwargs:
            timeout: 请求超时时间
        :return:
        """
        from common.g.g import G_LOGGER

        _default_header = _DEFAULT_GET_HEADER if method == "GET" else _DEFAULT_POST_HEADER
        _default_header.update(kwargs.get("headers", {}))
        kwargs['headers'] = _default_header
        timeout = kwargs.pop("timeout", None) or self.__timeout
        parse_with_json = kwargs.pop("parse_with_json", None)
        if self.__client is None:
            raise RuntimeError("client is already closed")
        try:
            async with self.client.request(method, url, timeout=timeout, **kwargs) as a_cli:
                if self._parse_with_json(a_cli, parse_with_json):
                    res = await a_cli.json()
                else:
                    res = await a_cli.read()
                if a_cli.status < 400:
                    return res
                G_LOGGER.error(f"request failed: {url}")
                return {"errcode": status.HTTP_500_INTERNAL_SERVER_ERROR, "errmsg": a_cli.reason, "body": res}
        except (asyncio.TimeoutError, ServerTimeoutError) as e:
            G_LOGGER.error(f"request timeout: {url} {e}")
            return {"errcode": status.HTTP_504_GATEWAY_TIMEOUT, "errmsg": "Service Timeout"}
        except ClientResponseError as e:
            G_LOGGER.error(f"response parse failed: {url} {e}")
            return {"errcode": status.HTTP_500_INTERNAL_SERVER_ERROR, "errmsg": e.message}
        except Exception as e:
            G_LOGGER.exception(f"response parse failed: {url} \n{e}")
            return {"errcode": status.HTTP_500_INTERNAL_SERVER_ERROR, "errmsg": str(e) or "Unknown Error"}

    async def get(self, url: str, parse_with_json=False,  **kwargs):
        """

        :param url:
        :param parse_with_json: 是否对响应进行json反序列化
        :param kwargs:
        :return:
        """
        return await self._request(method="GET", url=url, parse_with_json=parse_with_json, **kwargs)

    async def post(self, url: str, parse_with_json=False, json=None, data=None, **kwargs):
        return await self._request(method="POST", url=url, parse_with_json=parse_with_json, json=json, data=data, **kwargs)

    async def put(self, url: str, parse_with_json=False, json=None, data=None, **kwargs):
        return await self._request(method="PUT", url=url, parse_with_json=parse_with_json, json=json, data=data, **kwargs)

    async def request(self, url, method, parse_with_json=True, **kwargs):
        return await self._request(method, url, parse_with_json=parse_with_json, **kwargs)

    async def form(self, url, method, parse_with_json=True, fields: List[Dict[str, Any]] = None, files: List[Dict[str, Any]] = None, **kwargs):
        """
            form表单请求
            files:
                [
                    {
                        "filename": "",  # 文件名
                        "content": "",  # bytes 文件内容
                        "name": ""  # 字段名
                    },
                    ...
                ]
            fields:
                [
                    {
                        "name": "",  # 文件名
                        "content": "",  # 内容
                        "name": ""  # 字段名
                    },
                    ...
                ]
        :param url:
        :param method:
        :param parse_with_json:
        :param fields: 字段列表
        :param files: 文件列表
        :param kwargs:
        :return:
        """
        data = FormData()
        headers = {"Content-Type": f"multipart/form-data; boundary={data._write.boundary}"}
        if fields:
            for f in fields:
                data.add_field(name=f["name"], value=f["content"])
        if files:
            for f in files:
                data.add_field(name=f["name"], filename=f["filename"], value=BytesIO(f["content"]))
        fd = data()
        h = kwargs.pop("headers", {})
        h.update(headers)
        return await self._request(url=url, method=method, parse_with_json=parse_with_json, headers=h, data=fd, **kwargs)

    async def _request_v2(self, method: str, url: str, **kwargs) -> Union[dict, ClientResponse]:
        """
        相比_request 这里会返回response的content和header
        :param url:
        :param method:
        :param parse_with_json:
        :param kwargs:
        :return:
        """
        from common.g.g import G_LOGGER

        _default_header = _DEFAULT_GET_HEADER if method == "GET" else _DEFAULT_POST_HEADER
        _default_header.update(kwargs.get("headers", {}))
        kwargs['headers'] = _default_header
        timeout = kwargs.pop("timeout", None) or self.__timeout
        if self.__client is None:
            raise RuntimeError("client is already closed")
        parse_with_json = kwargs.pop("parse_with_json", False)
        try:
            async with self.client.request(method, url, timeout=timeout, **kwargs) as a_cli:
                if parse_with_json:
                    return await a_cli.json()
                return a_cli
        except (asyncio.TimeoutError, ServerTimeoutError) as e:
            G_LOGGER.error(f"request timeout: {url} {e}")
            return {"errcode": status.HTTP_504_GATEWAY_TIMEOUT, "errmsg": "Service Timeout"}
        except ClientResponseError as e:
            G_LOGGER.error(f"response parse failed: {url} {e}")
            return {"errcode": status.HTTP_500_INTERNAL_SERVER_ERROR, "errmsg": e.message}
        except Exception as e:
            G_LOGGER.exception(f"response parse failed: {url} \n{e}")
            return {"errcode": status.HTTP_500_INTERNAL_SERVER_ERROR, "errmsg": str(e) or "Unknown Error"}

    async def request_v2(self, url, method, **kwargs):
        return await self._request_v2(method, url, **kwargs)


async def init_client(loop=None):
    loop = loop or asyncio.get_event_loop()
    return Client(loop=loop)


if __name__ == '__main__':
    import asyncio
    loop = asyncio.get_event_loop()

    async def test(lop):
        client = Client(lop)
        res = await client.get("https://www.baidu.com")
        await client.close()

    loop.run_until_complete(test(loop))





